<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/5/14 0014
 * Time: 下午 3:35
 */

namespace App\Common;
use App\Model\User;
use App\Utility\RedisTools;
use App\Model\GameType;
use App\Utility\BaseEnum;
use App\Model\GameOrder;
use App\GameService\games\PlayHelp;
use App\Enum\CardEnum;
use App\Enum\SysEnum;
use App\Utility\DBTools;
use EasySwoole\EasySwoole\Task\TaskManager;

/**
 * 棋牌的配置参数分类
 * 保存当前棋牌的说明数据
 * @author XCH
 *
 */
class GameCommon
{
    protected static $instance = null;
    protected static $login_longtime = 7200;
    //用户状态
    const ROOM_STATUS = [0=>'禁用',1=>'正常',2=>'锁定',-1=>'删除'];
    /**
     * TODO 特殊结算游戏ID
     */
    public static $hundGameTypeList = array(105);
    
    /**
     * TODO 构造函数
     * @param array $options 参数
     * @access public
     */
    public function __construct($options = [])
    {
    }
    
    
    
    /**
     * TODO 取游戏类名称及相关字段数据；
     * @param number $gameId
     * @param string $field
     * @return String 字段值：如gameKey
     */
    public static function getGameName($gameId=0,$field = "gameKey") {
        
        $allGameKey = SysEnum::ONLINE_GAMES_ALL_KEY ;
        $gameInfo = RedisTools::Pool_hGet($allGameKey,$gameId);                        //取游戏名称；
        $gameInfo = !empty($gameInfo) ? json_decode($gameInfo,true): false ;
        if (!$gameInfo) {
            $dbmodel = new GameType();
            $gameInfo = $dbmodel->getInfo($gameId,FALSE); //取一个数组；
            if(!empty($gameInfo)){
                RedisTools::Pool_hSet($allGameKey, $gameId,json_encode($gameInfo));   //存起来；
            }
        }
        //返回值：$gameInfo
        return (!empty($gameInfo) && isset($gameInfo[$field]) && !empty($gameInfo[$field])) ? $gameInfo[$field] : ""; //取对应值
    }
    
    
    //加入游戏列表；
    protected static function  joinGameList($roomId,$player = false){
        $oneuid = isset($player['uid']) ? intval($player['uid']) : 0 ;  // 玩家ID值；
        //统一把所有参数游戏的人都放在游戏列表中去；
        $fjUser = BaseComm::distMemoryPlayer($roomId,FALSE);
        BaseEnum::Logger('Games-(joinGameList)-列队数据-->>>' . json_encode($fjUser), 'user-login');
        if(isset($fjUser) && !empty($fjUser)){
            $fjUser[$oneuid] = $player;
        }else{
            $fjUser = [$oneuid => $player];
        }
        BaseComm::distMemoryPlayer($roomId,TRUE,$fjUser);   //玩家 1 加入列表
        BaseEnum::Logger('Games-(joinGameList)-加入列队-->>>' . json_encode($fjUser), 'user-login');
        return isset($fjUser) ? $fjUser : FALSE;    //返回当前游戏中的总人数
    }
    
    /**
     * TODO组队创建游戏桌号
     * TODO 房间桌号都是自动分配处理的；达到人数集满就可以开局
     * @param int $roomId
     */
    protected static function groupRoomsUser($roomId,$roomList = false,$fjInfo = false ,&$roomNo = false,&$allRecUids = FALSE) {
        $fjUserList = array();
        if(!$roomList){  
            $roomList = BaseComm::distMemoryPlayer($roomId , FALSE);  //没有排队人员
        }                          
        
        $allRoomUserIds = !empty($roomList) ? array_keys($roomList) : false ;
        $gameId = isset($fjInfo['gameId']) ? intval($fjInfo['gameId']) : 0 ;
        $playNum = isset($fjInfo['playNumber']) ? intval($fjInfo['playNumber']) : 1 ;
        $minDiMoney = isset($fjInfo['everyPoints']) ? intval($fjInfo['everyPoints']) : 1 ;
        $minPeop = isset($fjInfo['minPlayer']) ? intval($fjInfo['minPlayer']) : 0 ;          //最小限，0为不限；999;
        $maxPeop = (isset($fjInfo['maxPlayer']) && intval($fjInfo['maxPlayer']) > 0)  ? intval($fjInfo['maxPlayer']) : 0;    //最大限，0为不限；999;
        $duiNum = !empty($allRoomUserIds) ? sizeof($allRoomUserIds) : 0 ;
        
        //当前游戏中的真实现家数
        $gameDoRealNum = 0;
        
        //取几个人参数匹配成一个游戏队；
        $getsmall_num = $minPeop;                    //取最少人数开始；
        if ($duiNum > 0 && $duiNum >= $minPeop) {    //满足最少条件即可生成一局；
            if(isset($maxPeop) && $maxPeop > $minPeop && $maxPeop <= 999 && $duiNum >= $minPeop + 2 ){ //超出2个以上才随机取
                $maxPeop = (isset($duiNum) && $duiNum < $maxPeop) ? $duiNum : $maxPeop ;    //不能超过队的值：
                $getsmall_num = rand($minPeop,$maxPeop);                                    //取随机人数目
            }
            
            //取随机匹配用户；
            $getUids = (!empty($allRoomUserIds) && $duiNum >= $getsmall_num && $getsmall_num > 0) ? array_rand($allRoomUserIds,$getsmall_num) : false ;
            $getUids = ($getsmall_num == 1 && $getUids !== FALSE && is_numeric($getUids)) ? [$getUids]: $getUids; //只取一个值时返回一个数字
            BaseEnum::Logger("创建游戏检测：[是否房间内人数充足: {$duiNum}] 匹配人员数：{$getsmall_num} --SUCCESS== >> " . json_encode($getUids), 'user-login');
            if (!empty($getUids)) {
                $seat = 0;
                foreach ($getUids as $vin){
                    $vuid = isset($allRoomUserIds[$vin]) ? intval($allRoomUserIds[$vin]) :0 ;
                    $vplayinfo = isset($roomList[$vuid]) ? $roomList[$vuid] : 0;
                    if (isset($vplayinfo) && !empty($vplayinfo)) {
                        //TODO 真实玩家数目 +1
                        $gameDoRealNum += 1;
                        
                        $vplayinfo["fullName"] = $vplayinfo["name"];
                        $vplayinfo["name"] = BaseComm::hiddenUserName($vplayinfo["name"]);                        
                        //分配座位号；
                        $vplayinfo["seatNo"] = $seat;
                        $vplayinfo["maxQzMutle"] = 3;   //牛牛3倍
                        //TODO 抢庄倍数： 抢庄玩家本局能承受的最大赔付金额是低注的多少倍数。
                        if ($gameId == 102) {
                            $countMaxMut = (isset($vplayinfo["points"]) && floatval($vplayinfo["points"]) > $minDiMoney) ? intval($vplayinfo["points"]/$minDiMoney) : 1;
                            $vplayinfo["maxQzMutle"] = (isset($countMaxMut) && $countMaxMut > 200) ? 200 : $countMaxMut; //最小倍数
                        }else if($gameId == 112){
                            $vplayinfo["maxQzMutle"] = 4;                           //牌九抢庄倍数最大4倍
                        }
                        unset($vplayinfo['group_id'],$roomList[$vuid]) ;            //清掉ID值；
                        $fjUserList[$seat] = $vplayinfo;                            //取用户信息；
                        $seat ++;                                                   //座位号分配
                    }
                }
                //是否列队中还有人员
                BaseComm::distMemoryPlayer($roomId,TRUE,$roomList);                    //保存用户数据
                
                //生成房间号等信息
                if(!empty($fjUserList)){
                    $allRecUids = !empty($fjUserList) ? array_column($fjUserList, "agentId") : FALSE;
                    $allRecUids = !empty($allRecUids) ? array_unique($allRecUids) : FALSE ; // 游戏数据
                    //TODO 游戏开始=== 获取房间编号
                    $roomNo = self::openGameSuccess($gameId, $roomId, $fjUserList,$playNum,$gameDoRealNum);
                    if(!$roomNo){
                        BaseEnum::Logger("创建游戏检测：(joinGameRidUsers)-开启失败..>>>" . json_encode($fjUserList), 'user-login');
                        return FALSE;
                    }
                }
            }
        }else{
            BaseEnum::Logger("创建游戏检测：(joinGameRidUsers)-[是否房间内人数: {$duiNum}] ,至少需要人数：{$getsmall_num} --才能开始游戏...>>>" . json_encode($allRoomUserIds), 'user-login');
        }
        return !empty($fjUserList) ? $fjUserList : FALSE;
    }
    
    /**
     * TODO 游戏开局成功
     * TODO 组装房间的状态信息；和玩家数据(fjStatus: 0 == 房间抢庄 )
     */
    public static function openGameSuccess($gameId,$roomId,$gameUsers , $playNum = 1,$gamePeop = 0,$extendsArray=FALSE){
        if(!$gameId || !$roomId || empty($gameUsers)) return FALSE;
        $gameNo = BaseComm::getRoomUnioxNo($roomId);        //房间编号
        $gamePeop = ($gamePeop>0) ? $gamePeop : sizeof($gameUsers);
        $fjPlays = array(
            "isOpen"    => 1,
            "gameId"    => $gameId,
            'grid'      => $roomId,
            "gameNo"    => $gameNo,
            "fjStatus"  => CardEnum::GAME_STEP_0,
            "currInning" => 1,
            "playNumber"=> $playNum ,                                       //最多玩多少局才能结束；
        );
        if (!empty($extendsArray)) {
            $fjPlays = array_merge($fjPlays,$extendsArray);
        }
        $fjPlays["isFirst"] = 1 ;                                           //开始第一轮
        $fjPlays["create"] = BaseEnum::getNowTime(FALSE);                   //创建时间
        $fjPlays["lists"] = $gameUsers;                                     //创建时间
        BaseComm::distMemoryRooms($gameNo ,TRUE,$fjPlays);                  //保存用户数据
        //首先用FD查询当前玩家进过哪些房间信息；
        $allUids = array_column($gameUsers, "uid");
        BaseComm::distLastGameNoListRedis($allUids,TRUE, json_encode([$gameNo,$roomId]));        //缓存玩家最近的房间号；
        BaseEnum::Logger("组建游戏新桌号：(joinGameRidUsers)-=={$gameNo} -- >>>" . json_encode($fjPlays), 'user-login');
        //$redisobj -> setRedisValue(self::ONLINE_ROOMS_LIST_KEY.$roomNo, json_encode($fjPlays),self::$login_longtime);               //缓存游戏信息；
        
        //TODO 在线玩家数据（加入）
        BaseComm::changeOnLines($gameId,$gamePeop,0);
        return $gameNo;
    }
    
    
    
    
    /**
     * TODO 加入或创建虚拟房间号； 
     * @param int $roomId
     */
    public static function joinXYRooms($roomId,$roomInfo=FALSE,$player = false,&$allRecUids = FALSE) {
        $currentFd = isset($player['fd']) ? intval($player['fd']) : 0 ;
        if(!$roomId || !$roomInfo || $currentFd <=0 )return false ;        
        $roomNo = false;
        BaseEnum::Logger('匹配测试：(joinXYRooms)-RoomId='. $roomId. '-- player=='.json_encode($player), 'user-login');
        
        //加入排队
        $roomList = self::joinGameList($roomId,$player);
        
        //是否可以组队成功
        $fjPlayerInfo = self::groupRoomsUser($roomId,$roomList,$roomInfo,$roomNo,$allRecUids);
        
        //是否成功
        if (isset($fjPlayerInfo) && !empty($fjPlayerInfo) && !empty($roomNo)) {
            return ['ifOpen'=>1,"roomNo"=>$roomNo,"info"=>$fjPlayerInfo];
        }
        //失败
        return FALSE;
    }

    /**
     * TODO 房间是否已开局；等待时间内；
     * @param int $roomId
     * @param boolean $roomNo
     */
    public static function roomIsOpening($roomId,$roomNo=false,$handUix=false,&$gamedata = false){
        if(!$roomId ||!$roomNo) return false;
        
        //获取游戏内的数据；
        $fjInfo = BaseComm::distMemoryRooms($roomNo);
        if(!$fjInfo) return false;
        $fjStatus = isset($fjInfo['fjStatus']) ? intval($fjInfo['fjStatus']) : 0 ;
        
        //默认为下庄等待，不判断 ；$handUix = BaseEnum::XYM_0X19 下注操作必须等抢庄之后再操作;
        if ($handUix === BaseEnum::XYM_0X19 && $fjStatus !== 1 ){
            return 'NOT_BET';   //不是下注时间；
        }
        //当前FD所在房间已经开局，
        if(isset($fjInfo["isOpen"]) && intval($fjInfo["isOpen"]) === 1){
            $gamedata = $fjInfo;
            return "OK"; 
        }
        return "WAIT";
    }


    /**
     * TODO 房间已开局写入DB；
     * @param int $roomId
     * @param boolean $roomInfo
     */
    public static function createRoomsPlayinfo($roomId,$room_No,$roomInfo=false,$fzUid = 0) {
        $model = new GameType();  //写一行数据；
        try {
            $gameId = isset($roomInfo['gameId']) ? intval($roomInfo['gameId']) : 0 ;
            $rName = isset($roomInfo['roomName']) ? trim($roomInfo['roomName']) : "" ; //房间名
            $minPeop = isset($roomInfo['minPlayer']) ? intval($roomInfo['minPlayer']) : 0;
            $users_num = isset($roomInfo['users_num']) ? intval($roomInfo['users_num']) : 0;
            $fjSmall = isset($roomInfo['smallPoints']) ? floatval($roomInfo['smallPoints']) : 0.0;
            $agentIds = $roomInfo["agentsIds"]  ?? FALSE;
            $otherinfo = array(
                "openStatus"=>1,
                "playerNum"=> $users_num,
                "smallPoints"=> $fjSmall,
                "agentsIds"=> !empty($agentIds) ? implode(',', $agentIds) : "", //上级代理ID列表
                "roomChairNum"=> (isset($roomInfo['maxPlayer']) && intval($roomInfo['maxPlayer'])>0) ? intval($roomInfo['maxPlayer']) : $minPeop,
                "playerInfo"=> isset($roomInfo['users']) ? BaseEnum::JsonEncode($roomInfo['users']): "",
                "openDateTime"=> date('Y-m-d H:i:s', time()),
                "created"=>time(),
            );
            //写入房间到DB中
            $res = $model -> createRoomsShow($gameId, $roomId,$room_No,$otherinfo);
            if(!$res){
                return false ;
            }
            return TRUE;
        } catch (\Exception $e) {
            BaseEnum::Logger('GameCommon-(createRoomsPlayinfo)-RoomId='. $roomId . $rName .'-- error=='.$e->getMessage(), 'user-login');
            return false ;
        }
    }
    
    /**
     * TODO 组装玩家下单信息
     * @param int $userId
     * @param array $fanjianInfo
     * @param array $editData
     * @param boolean $betFlag 是否为闲家下注，默认为抢庄
     */
    public static function createUserOrderFrom($userId,$fanjianInfo,$editData,$betFlag = false,$players=false){
        $userIds = !empty($userId) ? explode(',', $userId) : false ;
        $userLists = !empty($players) ? array_column($players,null,"uid") : false ;
        //查询用户信息；
        $orderInfo = [] ; //组装信息
        $commonField = array(
            "gameId" => isset($fanjianInfo["gameId"]) ? intval($fanjianInfo["gameId"]) :0 , //游戏类型ID
            "roomId" => isset($fanjianInfo["roomId"]) ? intval($fanjianInfo["roomId"]) :0 , //房间类型ID
            "tableId" => isset($fanjianInfo["id"]) ? intval($fanjianInfo["id"]) :0 ,
            "mainUserId" => isset($fanjianInfo["mainUid"]) ? intval($fanjianInfo["mainUid"]) :0 , //坐庄家
            "mutleDotts" => isset($fanjianInfo["mainMutl"]) ? intval($fanjianInfo["mainMutl"]) :0 , //坐庄家倍数
            "userCount" => isset($fanjianInfo["playerNum"]) ? intval($fanjianInfo["playerNum"]) :0 , //房间人数
            "gameNo" => isset($fanjianInfo["gameNo"]) ? trim($fanjianInfo["gameNo"]) :'' , //GameID游戏号
            "gameStartTime" => isset($fanjianInfo["openDateTime"]) ? trim($fanjianInfo["openDateTime"]) :'' , //开始时间
        );
        
        //玩家信息
        foreach ($userIds as $userId){
            $oneUser = isset($userLists[$userId]) ? $userLists[$userId] : false ;   //用户信息       
            $seeatId = isset($oneUser['seatNo']) ? $oneUser['seatNo'] : 0;          //座位号
            $agentId = isset($oneUser['agentId']) ? $oneUser['agentId'] : 0;        //上级代理
            $orUser = [
                "userId"=>$userId,
                "cellScore"=>0.0,
                "chairId"=> $seeatId,
                "channelId" => $agentId 
            ];
            $orUser["account"] =  $oneUser['fullName'] ?? ""; //玩家账号
            $orUser["lineCode"] =  $oneUser['lineCode'] ?? ""; //玩家标识
            $orUser["firstPoint"] =  $oneUser['points'] ?? 0 ; //初始进入游戏分
            if(!$betFlag){
                $orUser["mainUserId"] =  $userId;
                $orUser["mutleDotts"] =  isset($editData['mainMutl']) ? intval($editData['mainMutl']) : 1;
            } 
            $orderInfo[] = array_merge($commonField,$orUser);
        }
        return $orderInfo;
    }
    
    
    
    /**
     * TODO 抢庄和下注倍数据 ;写入DB的数据
     * @param string $room_No 房间号
     * @param boolean $savedata
     */
    public static function updateRoomsPInfo($room_No,$savedata = false,$players = false,&$error = '') {
        $model = new GameType();            //写一行数据；
        //去取玩家要下注的房间信息
        $fanjianInfo = $model -> getGameTableDetail($room_No); 
        $pType = isset($savedata['ptype']) ? intval($savedata['ptype']) : 1 ;        //默认为抢庄用
        $userId = isset($savedata['userId']) ? trim($savedata['userId']) : "" ;      //用户ID
        $userdata = isset($savedata['data']) ? $savedata['data'] : "" ;              //提交数据；
        $editData = [];
        $errorCode = BaseEnum::MSG_0XFB;
        try {
            BaseEnum::Logger('更新房间信息：(GameCommon) -- [ updateRoomsPInfo ]-RoomNo='. $room_No. '--user=='.json_encode($userdata), 'user-login');
            if(empty($players)) { 
                $players = self::getRoomPlayer($room_No,false,false);   //玩家信息
            }
            if(isset($fanjianInfo["openStatus"]) && intval($fanjianInfo["openStatus"]) === 1){                  //正常开局 订单
                if($pType == 2){        
                    $errorCode = BaseEnum::MSG_0XFC; //修改信息为下注倍数
                    $editData["betMutls"]  = !empty($userdata) ? BaseEnum::JsonEncode($userdata) : "";          //强制OBJECT
                    //组状下注信息；（庄主下单）
                    $playOrder = self::createUserOrderFrom($userId,$fanjianInfo,$editData,true,$players);       //玩家订单信息
                }else{
                    //组状下注信息；（闲家下注）
                    $editData["mainUid"] = intval($userId);
                    $editData["mainMutl"] = intval($userdata); //抢的倍数
                    $playOrder = self::createUserOrderFrom($userId,$fanjianInfo, $editData,false,$players);     //玩家订单信息
                }
                BaseEnum::Logger('更新房间DB：(updateRoomsPInfo) -- [ BeginTrans ]-RoomNo='. $room_No. '===EditArr==='. json_encode($editData).'- addOrder=='.json_encode($playOrder), 'user-login');
                
                if(!empty($editData)  && !empty($playOrder) ) {
                    BaseEnum::Logger('playOrder='. json_encode($playOrder). '-- editData=='.json_encode($editData), 'user-login');
                    
                    //执行DB操作
                    return BaseComm::DbQuery(function($DBObj) use($userId,$room_No,$playOrder,$editData){
                        $DBObj->startTransaction();      //开启事务
                        try {
                            $editRows = $DBObj ->where("gameNo", $room_No)->update("qc_game_display", $editData) ;      //修改房间信息
                            $addOrd =   $DBObj->insertMulti("qc_games_orders", $playOrder) ;
                            // 抢庄成功，生成庄家的订单信息
                            if(isset($editRows) && isset($addOrd) && intval($addOrd) >0 && intval($editRows) > 0){
                                BaseEnum::Logger('更新房间提交事务：----(updateRoomsPInfo) -- [ commitDB ]-RoomNo='. $room_No. '-- UserIds=='.$userId, 'user-login');
                                $DBObj->commit();         //提交成功
                                return TRUE;
                            }else{
                                BaseEnum::Logger('抢庄/下倍数失败:DB异常-RoomNo='. $room_No, 'user-login');
                                throw new \Exception("抢庄/下倍数失败:DB异常",BaseEnum::MSG_0XFC);
                            }
                        } catch (\Throwable $e) {
                            $DBObj->rollback();
                        }
                        return FALSE;
                    });
                }else{
                    BaseEnum::Logger('抢庄/下倍数失败:组装订单失败-RoomNo='. $room_No, 'user-login');
                    throw new \Exception("抢庄/下倍数失败:组装订单失败",$errorCode);
                }
            }else{
                BaseEnum::Logger('抢庄/下倍数失败:组装订单失败! RoomNo='. $room_No, 'user-login');
                throw new \Exception("抢庄/下倍数失败:组装订单失败!",BaseEnum::MSG_0XF6);  //房间无效
            }
        } catch (\Throwable $e) {
            $error = "Fail:" .$e ->getMessage();
            BaseEnum::Logger('出现异常：(updateRoomsPInfo) -- [ rollBackDB ]-RoomId='. $room_No ."  ErrorNo = ".  $e->getCode().  '-- error=='.$error, 'user-login');
        }
        return FALSE;
    }
    
    /**
     * TODO 玩家抢庄;写入缓存操作 REDIS
     * @param string $gameNo
     * @param int $loginFd
     * @param int $gameMut
     * @param string $downtype
     */
    public static function playerGetMain($gameNo,$loginFd,$gameMut = 0, $downtype = 0,$allFdSeat = FALSE,$gameObj = FALSE) {
        if(!$gameNo || !$loginFd || empty($allFdSeat)) return false ;
        $rediskey = SysEnum::ONLINE_BDATA_KEY;
        if(isset($downtype) && intval($downtype) === 1) {   
            $gameMut = (intval($gameMut) <= 0) ? 1 : $gameMut;      //默认下注一倍
            $rediskey =  SysEnum::ONLINE_BDATA_KEY."_MUT";             //下注倍数据存储的KEY 
        }
        $userNum = sizeof($allFdSeat);
        
        //返回所有玩家对应位置号；
        $get_Fsetid = !empty($allFdSeat) ? array_search($loginFd, $allFdSeat) : 0 ; //获取当前座位ID
        //取抢庄数据
        $downInfo = RedisTools::Pool_hGet($rediskey, $gameNo); 
        $downInfo = !empty($downInfo) ? json_decode($downInfo,true) : FALSE ;
        if(isset($downInfo) && !empty($downInfo)){
            $downInfo[$get_Fsetid] =  $gameMut;                        //抢庄倍数；
        }else{
            $downInfo = array($get_Fsetid => $gameMut);                //抢庄倍数；
        }
        RedisTools::Pool_hSet($rediskey, $gameNo, BaseEnum::JsonEncode($downInfo));
        
        //TODO 清掉定时器的功能；
        self::stopQiangZhuang($userNum, $gameNo,$downInfo,$downtype);
        return TRUE;
    }
    
    
    //TODO 抢庄牛牛等抢庄结束
    public static function stopQiangZhuang($userNum,$gameNo,$userQz = FALSE,$downtype = 0){
        BaseEnum::Logger("检测是否需要暂停(type={$downtype})： (stopQiangZhuang) -- [ Wait ]-gameNo={$gameNo},qzlist=". BaseEnum::JsonEncode($userQz)."==totalNum:". $userNum, 'user-login');
        $qz_Ok = ($downtype===0 && sizeof($userQz) >= $userNum) ? TRUE : FALSE ;            //所有人都抢庄完；
        $bet_Ok = ($downtype===1 && sizeof($userQz) + 1 >= $userNum) ? TRUE : FALSE ;       //所有闲家都下注完；
        if (!empty($userQz) && ($qz_Ok || $bet_Ok)) {                                       //满足任一条件就强行结束定时器
            PlayHelp::getInstance()->setCommTimerStop($gameNo,TRUE);                        //停掉定时器
            BaseEnum::Logger("所有人操作完：type={$downtype}========：(playerGetMain) -- [ STOP ]-RoomId=". $gameNo ."  =======================设置为1,中断操作========>>>>>>" . 1, 'user-login');
        }
    }
    
    /**
     * TODO 获取最高的FD用户信息
     * @param array $downInfo
     * @param boolean $playMfd
     * @param number $maxMutl  返回最大的抢的倍数据值；
     * @return boolean|number|mixed
     */
    protected static function getGameQzMaxFd($downInfo,$playMfd = false,&$maxMutl = 1,$maxQzTimes = FALSE){
        $lastFdVal = 0 ;
        $maxFdList = [];
        if (!isset($downInfo) || empty($downInfo) ) { return false;}
        //TODO 限每个玩家下注倍数的值(通过UID==> 最高值限定)
        if (!empty($maxQzTimes) && !empty($downInfo)) {
            foreach ($downInfo as $r => $rqz){
                $maxqzv = $maxQzTimes[$r] ?? FALSE ;    //有限值
                if(!$maxqzv  || ($maxqzv > 0  && $rqz > 0 && intval($rqz) > intval($maxqzv))){        //清掉无效的值：限定不从庄
                    BaseEnum::Logger("检测到玩家抢庄倍数超最高值：(getGameQzMaxFd)-中[Fd={$r}，抢{$rqz}倍]--计算最大倍数：{$maxqzv}-->>>-暂放弃选为庄家;", 'user-login');
                    $downInfo[$r] = 0;
                }
            }
        }
        $maxMutl = !empty($downInfo) ? max($downInfo) : FALSE;                          //取最大值==最大倍数；
        BaseEnum::Logger("计算最大倍数：(GamesCommon-getGameQzMaxFd)-中最大的FD [Fd=Array]--最大倍数：{$maxMutl}-->>>--playermoney=".json_encode($playMfd)."=>maxTimes=".json_encode($maxQzTimes), 'user-login');
        if (isset($maxMutl) && intval($maxMutl) > 0) {      //有抢过倍数大于=1的
            $countRes   = array_count_values ($downInfo);   //对比倍数； 取最大值；
            $count_max_num = isset($countRes[$maxMutl]) ? intval($countRes[$maxMutl]) : 0 ;
            //最大数据；
            if(isset($count_max_num) && $count_max_num > 1){
                //寻找最大值的多个用户；
                BaseEnum::Logger("计算最大倍数：(getGameQzMaxFd)-找FD [Fd=Array]--抢{$maxMutl}倍数人数（ > 1）：{$count_max_num}-->>>--playermoney=".json_encode($countRes), 'user-login');
                foreach ($downInfo as $onefd => $onemax){
                    if(intval($maxMutl) === intval($onemax)){
                        $maxFdList[] = $onefd;          //最大值
                    }
                }
                $lastFdVal = !empty($maxFdList) ? reset($maxFdList) : 0 ;
                //大于2个用户抢的倍数相同；
                if (isset($playMfd) && !empty($playMfd) && !empty($maxFdList) && sizeof($maxFdList) > 1) {
                    //比较这几个用户的积分最高；
                    $up_points = isset($playMfd[$lastFdVal]) ? floatval($playMfd[$lastFdVal]) : 0 ; //余额的钱；
                    foreach ($maxFdList as $vvfd){
                        if (isset($playMfd[$vvfd]) && floatval($playMfd[$vvfd]) > $up_points) {
                            $lastFdVal = $vvfd; //最后庄家FD值；
                        }
                    }
                }
            }else{
                $lastFdVal = array_search($maxMutl, $downInfo);
            }
        }else{
            $maxMutl = 1 ;                          //取当前的FD作为最高值随机最少一倍
            $lastFdVal = array_rand($downInfo,1);   //随机索引就是FD
        }
        return $lastFdVal;
    }
    
    
    /**
     * TODO 获取缓存房桌上的玩家信息
     * @param string $roomNo 房间号
     */
    protected static function getRoomPlayer($roomNo , $redisobj = false,$ifout = false) {
        if(!$roomNo) return false ;
        $allData = BaseComm::distMemoryRooms($roomNo);
        if(isset($allData["isOpen"]) && isset($allData["lists"]) && !empty($allData["lists"])){
            return $allData["lists"];//玩家信息
        }
        return FALSE;
    }
    
    
    
    /**
     * TODO 计算这个房间谁坐庄(带条件限定)；
     * @param string $roomNo
     * @param array $player 主人信息
     * @param array $maxQzTimes 每个人抢庄倍数限定
     * @return boolean
     */
    public static function countRoomMaimZhuang($roomNo ,$player = false ,& $notQzUser= FALSE, $maxQzTimes = FALSE) {
        $rediskey = SysEnum::ONLINE_BDATA_KEY;
        $redisusekey = SysEnum::ONLINE_ROOMS_LIST_KEY.$roomNo;
        $zjUser = false ;
        $qzMaxMutl = 0;
        $fjDetail = BaseComm::distMemoryRooms($roomNo);                           //房间内信息 
        $playerList = isset($fjDetail['lists']) ? $fjDetail['lists'] : [];      //房间内的人数
        $allPSeat = !empty($playerList) ? array_column($playerList, "seatNo") : false ;
        $player_Moneys = !empty($playerList) ? array_column($playerList, "points","seatNo"): false ;
        if(isset($fjDetail['isOpen']) && intval($fjDetail['isOpen']) === 1 && !empty($playerList) && !empty($allPSeat)){    //游戏抢庄状态
            //抢庄的倍数据REDIS值；
            $downInfo = RedisTools::Pool_hGet($rediskey,$roomNo);
            $downInfo = !empty($downInfo) ? json_decode($downInfo,true) : FALSE;
            $notQzUser = [];  //统计还有几个玩家不抢庄的；
            foreach ($allPSeat as $ssid){
                if (isset($downInfo) && !empty($downInfo) && is_array($downInfo) && array_key_exists($ssid, $downInfo)) { //查询玩家是否已下过庄
                    continue ;
                }
                $notQzUser[] = ["seatNo"=> $ssid,"mutdotts" => 0, "self"=> 1];  //不抢的主动推信息 
            }
            BaseEnum::Logger("定时器计算谁坐庄：---all >>> " . json_encode($allPSeat) ."--qz:=".json_encode($downInfo), 'user-login');
            if(empty($downInfo)){ //都没抢；都为0
                $downInfo = array_column($playerList, "betmoney","seatNo") ; //取0的值
            }
            //TODO  对比倍数； 取最大值；
            $maxPfd = self::getGameQzMaxFd($downInfo , $player_Moneys , $qzMaxMutl, $maxQzTimes);   
            $zjUser = isset($player[$maxPfd]) ? $player[$maxPfd] : false ;
            if ($maxPfd !== false && $maxPfd >=0  && !empty($zjUser) && isset($qzMaxMutl)) {    //找到最优秀的FD用户值；
                
                //计算找到庄家FD；标识为为庄家；$maxPfd＝座位号
                $playerList[$maxPfd]["mutdotts"] = $qzMaxMutl;
                $playerList[$maxPfd]["mainflag"] = 1;
                $zjUser["mutdotts"] = $qzMaxMutl;       //抢庄倍数
                BaseEnum::Logger("定时器计算谁坐庄：(countRoomMaimZhuang)-定时器计算 success 谁是庄家[seatNo={$maxPfd}]--倍数：{$qzMaxMutl}-->>> " . $roomNo ."--player=".json_encode($player_Moneys), 'user-login');
                
                //存储值；
                $fjDetail['fjStatus'] = CardEnum::GAME_STEP_1;              //抢庄成功，进入下注
                $fjDetail['lists'] = $playerList;
                
                //是否列队中还有人员
                BaseComm::distMemoryRooms($roomNo,TRUE,$fjDetail);           //保存用户数据
                BaseEnum::wirteRedisData($redisusekey,BaseEnum::JsonEncode($fjDetail));  //缓存；
                return $zjUser;
            }
        }
        return FALSE;
    }
    
    
    //TODO 开出的号码选择
    public static function checkFirstCards($gameId,& $cardList=false,$roomId = false) {
        $rediskey = SysEnum::BASEDB_GAMERESULT_KEY . $roomId ;
        $pdata = RedisTools::Pool_get($rediskey);
        $pdata = !empty($pdata) ? json_decode($pdata,TRUE) : FALSE; //处理数据结果
        $result = !empty($pdata) ? array_values($pdata) : FALSE;
        if(!empty($cardList) && !empty($result)){
            foreach ($cardList as $ok => $oneval){
                if (isset($result[$ok]) && !empty($result[$ok]) && sizeof($result[$ok]) == sizeof($oneval)) {  //号码有效才启用
                    $cardList[$ok] = $result[$ok];
                    BaseEnum::Logger("使用预先定义好的牌值：座位seatNo：{$ok}  >>>--cards=".BaseEnum::JsonEncode($result[$ok]), 'user-login');
                }
            }
        }
    }
    
    /**
     * TODO创建牌内容
     * TODO根据不同的类型
     */
    public static function createCardList($gameId,$room_No,$commonConfig = false) {
        $gameModel = new GameType();                                                            //创建游戏参数
        $gameType = $gameModel ->getList(20);
        $allTypes = !empty($gameType) ? array_column($gameType, "gameKey","gameId") : false ;   // 大类
        $gameUtype = isset($allTypes[$gameId]) ? $allTypes[$gameId] : "";                       //游戏类型
        $allPlayer = self::getRoomPlayer($room_No,false,false) ; //房间用户信息
        if(!$allPlayer || !$gameUtype) return false;
        $allSeat = array_column($allPlayer,"fd","seatNo");
        $playerNum = sizeof($allPlayer);
        BaseEnum::Logger("创建牌 总人数：{$playerNum}人数 >>>--seatinfo=".BaseEnum::JsonEncode($allSeat), 'user-login');
        $seatCards = [];
        //每个FD对应的数据
        $cardList = [];
        $gameJu = substr("{$room_No}", -10); //取局号后面的10位 统一标准值； 
        $gameInning = intval(substr(substr("{$room_No}", -11) , 0 , 1)) + 1; 
        if(!empty($gameUtype) && $gameUtype != "ALL"){
            
            //TODO 生成牌规则
            $cardList = BaseComm::createCards($gameUtype, ["nums"=> $playerNum ,"gameJu"=> $gameJu,"currInning" => $gameInning]);   //创建棋牌数据；
            $roomId = $commonConfig['roomId'] ?? 0;                             //TODO 房间ID
            $CellUserIds = $commonConfig['CellUserIds'] ?? FALSE;               //TODO 真实玩家ID座位号
            self::checkFirstCards($gameId,$cardList,$roomId);                   //TODO 检验是否有预存号码
            
            
            //TODO 处理玩家牌结算点（真实玩家牌值 + 要比机器人玩家牌  ===== 小一倍）
            if (TRUE) {
                PlayCommon::gameKillPlayers($gameId,$CellUserIds,$cardList);
            }
            
            BaseEnum::Logger("棋牌数据 ：GamesCommon-(createCardList),gameJu={$gameJu},currInning={$gameInning},--cards = ".json_encode($cardList), 'user-login');
            if(empty($cardList) || !is_array($cardList)){
                BaseEnum::Logger("生成棋牌数据：GamesCommon-(createCardList)- -失败!!!! = ".json_encode([$room_No,$gameId,$gameUtype]), 'user-login');
                return false;
            }
            //把牌的数据写到DB中====show表
            $res = $gameModel->editRoomShow($room_No,["chessContent" => json_encode($cardList)],"gameNo");
            if(!$res) return false ;
            
            $BsgameIds = [102,112];                                             //号码不满4个要补全的情况
            $gameCards = [];                                                    //组装数据[座位号－－－棋牌数据]
            if($gameId >0 && in_array($gameId, $BsgameIds) &&  $playerNum < 4){                             //二八默认4个位的人数；
                for($onse = $playerNum; $onse < 4 ; $onse ++){ $allSeat[$onse] = -1; } //加满4个人
            }
            BaseEnum::Logger("分配每个位置：GamesCommon-(allSeat) => ".json_encode($allSeat), 'user-login');
            foreach ($allSeat as $sitidd => $sitFd){
                $cards = isset($cardList[$sitidd]) ? $cardList[$sitidd] : [];   //取座位号的值：
                if(!$cards) continue ;
                //按不同的棋牌类型来处理( 默认都是全数字表示 )
                /* if (in_array($gameId,[101,103])) { $cards = array_column($cards, "key"); } */
                if ( $gameId == 116 && !isset($gameCards["common"])) {
                    $gameCards["common"] = isset($cardList['common']) ? $cardList['common'] : [];   //德洲扑克有公共牌---->存起来放在内存中处理
                }
                $gameCards[$sitidd] = $cards;              //保存每个位置的棋牌 
                $oneLine = array("seatNo" => $sitidd ,"cards" => $cards , "dottVal" => -1);
                if(isset($sitFd) && intval($sitFd) > 0 ){
                    $seatCards[$sitFd] = $oneLine; 
                }
            }
            
            PlayHelp::getInstance()->setRoomCards($room_No,$gameCards);         //写入牌到内存数据；
            BaseEnum::wirteRedisData(SysEnum::BASEDB_GAMECARD_TWO_KEY,BaseEnum::JsonEncode($gameCards),$room_No);  //内存数据缓存写入Redis；
        }
        return $seatCards ;
    }

    
    
    /**
     * TODO 检查所有玩家的输赢金额问题(庄家不能赢超过自身携带金额)
     * @param int $gameId 游戏ID
     * @param array $data 结算数据
     * @param boolean $playList 玩家列表(包含分信息)
     */
    public static function checkUserDealPrice($gameKEY,& $dealRes = false,$zjUserId = 0){
        $playBets2 = [];
        if(!$dealRes || !$gameKEY || !$zjUserId) { return FALSE; }
        //TODO  找出庄家的倍数，闲家下注倍数都要计算
        $playIds = array_column($dealRes,"card_mutle","uid");                       //下注倍数
        $playBets = array_column($dealRes,"money","uid");                           //下注分数
        $zjWin = isset($playBets[$zjUserId]) ? floatval($playBets[$zjUserId]) : 0 ; // 庄家输赢
        if (!$playIds || !$playBets) {
            return FALSE ;
        }
        unset($playBets[$zjUserId]);
        foreach ($playBets as $k1 => $vl2){$playBets2[$k1] = abs($vl2);}
        $zjUserPoints = AuthCommon::getLoginUserPoints($zjUserId);
        //echo "庄家ID：$zjUserId, points =  {$zjUserPoints}";
        if(!$zjUserPoints) return FALSE;
        $zjWinFee = abs($zjWin);  //操作分绝对值(正数)
        if($zjWin === 0) { 
            //庄家没有输赢 就不用（检查）
            return TRUE;
        }else if ($zjWin > 0 && $zjWinFee > $zjUserPoints){
            //TODO 对庄家： 二、 庄家赢的钱超过自身携带金额(先收赢来的玩家钱)
            $print_1 = "庄家ID：$zjUserId, 自身余额 ： {$zjUserPoints}，本次赢的金额：{$zjWinFee} ， 出现庄家赢钱超最大携带分值，不能正常交易=>  闲家输的钱 ： ".json_encode($playBets);
            BaseEnum::Logger($print_1,"user-login");
            //TODO 取闲家分数（占比操作）
            $totalFen = array_sum($playBets2);
            foreach ($playBets as $puid => $pmoney) {
                $tempFee = round((abs($pmoney)/$totalFen) * $zjUserPoints,2);
                $playBets[$puid] = (floatval($pmoney) < 0) ? 0 - $tempFee : $pmoney;
            }
            $playBets[$zjUserId] = $zjUserPoints;
            $print_2 = "检查数据后重新计算：结算正确的数据：".json_encode($playBets);
            BaseEnum::Logger($print_2,"user-login");
        }else if ($zjWin < 0 &&  $zjWinFee > $zjUserPoints){
            //TODO 对庄家来说考虑两种情况： 一、 庄家不够输的情况
            $print_1 = "庄家ID：$zjUserId, 自身余额 ： {$zjUserPoints}，本次输的金额：{$zjWinFee} ， 出现庄家输分超过携带分值，不能正常交易=>  闲家赢的钱 ： ".json_encode($playBets);
            BaseEnum::Logger($print_1,"user-login");
            //TODO 取闲家分数（占比操作）
            $totalFen = array_sum($playBets);
            foreach ($playBets as $puid => $pmoney) {
                $playBets[$puid] =  round((abs($pmoney)/$totalFen) * $zjUserPoints,2);
            }
            $playBets[$zjUserId] =0 - $zjUserPoints;
            $print_2 = "检查数据后重新计算：结算数据：".json_encode($playBets); 
            BaseEnum::Logger($print_2,"user-login");
        }else{  $playBets = FALSE; }
        
        //重新计算后处理
        if(!empty($playBets)){
            foreach ($dealRes as $r => $oneRes){
                $zjWater = 0 ;  //计算抽水的钱
                $pvuid = $oneRes['uid'] ?? 0;
                $pvmoney = $oneRes['money'] ?? 0;
                $newmoney = $playBets[$pvuid] ?? 0;
                if ($pvuid && array_key_exists($pvuid, $playBets) && floatval($newmoney) != floatval($pvmoney)) {  //有变动
                    $dealRes[$r]['money'] = floatval($newmoney) ; //换新余额
                    if(floatval($newmoney) > 0){
                        $zjWater = bcmul($newmoney, BaseComm::getSetWaterDott(), 2); // 最后计算抽水的钱；
                    }
                    $dealRes[$r]['water'] = floatval($zjWater) ; //换新抽水的钱
                }
            }
        }
        return TRUE;
    }
    
    
    /**
     * TODO 处理订单结算
     * @param int $gameRId
     * @param string $room_No
     * @param boolean $zjObject
     * @param boolean $useCards
     * @param boolean $roomDetail
     */
    public static function dealPlayMoney($gameRId,$room_No ,$zjObject=false , $useCards = false ,$roomDetail=false,& $realUserNotices = []){
        $returndata = FALSE;
        $gameModel = new GameType(); 
        $tbName = $gameModel->tbRoomDisplayName;
        if(!$roomDetail) {
            $roomDetail = $gameModel->getGameRoomDetail($gameRId); //房间信息
        }
        $gameUtype = isset($roomDetail['gpKey']) ? $roomDetail['gpKey'] : "";  //大类别名
        BaseEnum::Logger('结算数据：(dealPlayMoney)===>' . json_encode([$gameRId,$room_No ,$zjObject , $useCards,$roomDetail]), 'user-login');
        
        //TODO 通用结算入口；
        $dealRes = BaseComm::dealCards($gameUtype,$zjObject,$useCards,$roomDetail,$returndata);
        BaseEnum::Logger('结算输出：(dealPlayMoney)-->' . json_encode($returndata). " " .json_encode($dealRes), 'user-login');

        //结果出来后，要操作对应的用户身份上；
        $cardValue = isset($returndata['cardvalues']) ? $returndata ['cardvalues'] :"";
        if(!empty($dealRes) && !empty($returndata) && !empty($cardValue)) { 
            
            //桌子信息==> 更新用户的输赢信息；
            $errorCode = -1;
            
            //TODO 结算订单数据；
            //TODO 操作相关的注单信息；1、修改订单状态，补全订单信息； 2、用户资金交易数据处理； 3、收拾桌面数据，匹配列表清空；
            $result = self::dealUserOrder($gameRId,$room_No,$dealRes,$returndata,$errorCode);
            BaseEnum::Logger('输出结果：(dealUserOrder---Result)===-->' . json_encode($result), 'user-login');
            
            //TODO 操作数据 ＝＝＝＝ 组装成 WEB端要用的数据；
            if(!empty($result) && sizeof($result) > 0){ //结算成功；
                foreach ($dealRes as $r => $dealobj){
                    $oneuid = isset($dealobj['uid']) ? intval($dealobj['uid']):0 ;
                    $oneArr = isset($result[$oneuid]) ? $result[$oneuid]:FALSE ;
                    if(!empty($oneArr) && isset($oneArr['points'])){
                        $dealRes[$r]["agentId"] = $oneArr['agentId'] ?? 0;  //上级代理ID
                        $dealRes[$r]["userName"] = $oneArr['userName'] ?? "";//玩家用户名
                        $dealRes[$r]["points"] = floatval($oneArr['points']);
                        $dealRes[$r]["changeMoney"] = floatval($oneArr['changeMoney']);
                        if(isset($oneArr['isNation']) && intval($oneArr['isNation']) !== 3){
                            //TODO 游戏的真实玩家结算数据
                            $realUserNotices[$oneuid] = [
                                "userName"=> $oneArr['userName'] ?? "",
                                "roomId" => $gameRId,
                                "changeMoney"=>floatval($oneArr['changeMoney'])
                            ];
                        }
                    }
                }
                $whereOpt = ["roomId"=>$gameRId,"gameNo"=>$room_No ];
                
                //TODO 把牌局信息补全；
                $editDisplay = [
                    "playerInfo"=> json_encode($dealRes,JSON_FORCE_OBJECT),
                    "finishDateTime"=> BaseEnum::getNowTime(true),
                    "openStatus" => CardEnum::BR_GAME_STEP_5
                ];
                
                $reds =  DBTools::PoolUpdate($tbName,$editDisplay,$whereOpt);   //连接池更新操作
                //TODO $reds = $DBObj->where("roomId",$gameRId)->where("gameNo",$room_No)->update($gameModel->tbRoomDisplayName, $editDisplay);
                if(isset($reds) && intval($reds) >0){
                    BaseEnum::Logger('结算完成：(dealPlayMoney)===补全牌局信息-->res:'.$reds. "==== " . json_encode($editDisplay), 'user-login');
                }
                //TODO 数据处理成功；返回内容；
                return $dealRes;
            } 
        }
        return FALSE ; 
        
    }
    
    
    
    /**
     * TODO 处理玩家的注单信息
     */
    public static function dealUserOrder($gameRId,$room_No,$dealRes,$paramData=false,$errorCode=false) {
        $lastUserBlance = [];
        if(!$gameRId ||!$room_No || empty($dealRes)) return false;
        $gameId = intval(substr("{$gameRId}",0,3));
        $hundGameTypes = self::$hundGameTypeList ;
        $modelUser = new User();                //操作结算数据；
        $modelOrder = new GameOrder();          //订单数据；
        $DBObj = DBTools::getPoolDB();          //DB连接对象；
        $cardValue = isset($paramData['cardvalues']) ? $paramData ['cardvalues'] :"";
        $allUids = !empty($dealRes) ? array_column($dealRes, "uid") : false;
        $allUserOrder = !empty($dealRes) ? array_column($dealRes,null,"uid") : false;
        $todayStr = date('Y-m-d',time());
        if(!$allUids || empty($allUids) || !$cardValue || empty($allUserOrder)) {  return false ; }
        
        //所有操作表名
        $user_Table = $modelUser->tableName ;
        $user_TJ_Table = $modelOrder->tongjiTabName;
        $user_ORDER_Table = $modelOrder->tableName;
        $user_RECODE_Table = $modelOrder->recodeTabName;
        $userFields = ["uid","name","isNation","status","points","recUid",'recUser'];
        
        //$tongjiFields = ['uid','userName',"recUid","createDate","allBetScore","userCellScore","winScore","profitScore","revenueScore","gameBetNumber","agencyGetScore","sysXqPoint","profitNumber"];  //统计表字段： allBetScore   总消费金额； userCellScore 有效下注  winScore(只算赢的)  revenueScore （抽水统计） profitScore   盈利金额 （纯盈利）  = 获得钱 - 有效下注；
        $editOrder = ["cardValue"=> $cardValue,"gameEndTime"=> BaseEnum::getNowTime(true),"dealing" => 1,"dealtime"=> BaseEnum::getNowTime(FALSE) ];  //结算数据
        
        //TODO 开启事务(所有用户都同一时间加锁)；
        $DBObj->startTransaction();          //开启事务
        try {
            BaseEnum::Logger("事务DB操作：(startTransaction)===>开启事务 --> START ! ", 'user-login');
            //$tongjiList = $DBObj -> where('uid',$allUids,'in') -> where('createDate',$todayStr) -> get($user_TJ_Table , null,$tongjiFields);
            //$tongjiExits = !empty($tongjiList) ? array_column($tongjiList,"userName","uid"): FALSE;
            $totalUNum = $totalUpdate = 0;
            //查询用户余额数据
            $lists = $DBObj -> where('uid',$allUids,'in')->selectForUpdate(TRUE)->get($user_Table,null,$userFields);    //需要加锁操作数据；
            $userList = !empty($lists) ? array_column($lists,null,"uid"): false ;                                       //用户原余额
            //$userAgeIds  = !empty($lists) ? array_column($lists,null,"ancestor_ids"): false ;                           //所有头部
            $agentLists = [];
            //有金额变化的玩家就要分批操作；
            foreach ($allUserOrder as $yeUid => $uOrder){
                $uDetail = isset($userList[$yeUid]) ? $userList[$yeUid] : false ;               //用户信息
                if(!isset($uDetail) || empty($uDetail) || intval($uDetail["uid"]) !== $yeUid) {
                    continue;
                }
                $userName = isset($uDetail['name']) ? $uDetail['name'] :"";
                $recUid = isset($uDetail['recUid']) ? intval($uDetail['recUid']) : 0;
                $isNation = isset($uDetail['isNation']) ? intval($uDetail['isNation']) : 0;             //身份
                $userOldmoney = isset($uDetail['points']) ? floatval($uDetail['points']) :0 ;           //原余分
                
                //$antIds = (isset($uDetail['ancestor_ids'])&& !empty($uDetail['ancestor_ids'])) ? explode(',', $uDetail['ancestor_ids']) : false;
                //TODO 要统计的上级代理环节
                if ($isNation != 3 && $recUid > 0) {
                    $agentLists[$recUid] = $uDetail['recUser'] ?? "";
                }
                
                //TODO ==================== 统一字段：allBetScore 总下注分    ==========================//
                $allBets = isset($uOrder['allBetScore']) ?  floatval($uOrder['allBetScore']) : 0;           //总下注分
                
                //TODO 有效下注金额（放在 cellScore 字段，且为正数）= 用户赢分   (抽水后的金额)
                $cellScore = isset($uOrder['cellScore']) ?  floatval($uOrder['cellScore']) : 0;             //有效下注分
                
                //TODO 用户输赢的钱(玩家盈亏分数 =  玩家即时操作的余额  lastPoints )
                $profitMoney = isset($uOrder['lastPoints']) ? floatval($uOrder['lastPoints']) :0 ;           //玩家最后的赢分
                
                //TODO 用户输赢钱抽水 water 字段
                $watermoney = isset($uOrder['water']) ?  floatval($uOrder['water']) : 0;                     //抽水分
                
                //TODO 用户输赢钱基数(没有抽水之前的值)
                $money1 = isset($uOrder['money']) ?  floatval($uOrder['money']) : 0;
                
                
                //TODO 玩家赢分累计(输为0，只有赢的时候才有值)
                $winMoney = ($profitMoney>0) ? $profitMoney : 0;        //TODO 玩家赢分
                
                //TODO 验证数据：是否OK
                if(!$recUid || !$yeUid) continue;
                
                //TODO 有效用户数据加+1 
                $totalUNum ++ ;     
                
                //TODO 计算最后玩家输赢FEE 
                $last_money = bcadd($userOldmoney, $profitMoney , 3);                                //用户余额变动后；
                
                //TODO 玩家的钱不够输就是前面没有判断好直接异常
                if ($last_money < 0) {
                    throw new \Exception("结算订单异常[Uid={$yeUid}],gameNo:{$room_No}，玩家余额：{$userOldmoney}， 计算后剩余：{$last_money},结算失败 ",-1);
                }
                
                //TODO 订单字段：总下注金额  = 用户实际输赢的钱
                $orderFields = ["allBet" => floatval($allBets),"cellScore"=> floatval($cellScore),"profit" => floatval($profitMoney),"revenue" => floatval($watermoney)];
                
                //资金记录：
                $userRecodes = [
                    'uid' => $yeUid,
                    'addType' => SysEnum::RECODE_TYPE_INDEX3,         //下注输赢钱
                    'sn' => $room_No,
                    'gameId'=> $gameRId,
                    'oldPoint' => $userOldmoney,
                    'newPoint' => floatval($last_money),
                    'changePoint' => floatval($profitMoney),
                    'created' => BaseEnum::getNowTime(false),
                    "extraKey" => '' ,
                    "bz" => '玩家输赢',
                    'userName' => $userName
                ];
                //中奖次数
                $orderFields = array_merge($editOrder,$orderFields);
                BaseEnum::Logger("事务DB操作：(玩家)===>UID:{$yeUid} , 原始分：{$userOldmoney},修改后：{$last_money}，本局赢分：{$winMoney} --> 结算分：{$money1}, 余额变动额：{$profitMoney}", 'user-login');
                
                //========================================操作DB : $profitMoney 变更分 ==========================================//
                
                $user_rows = 1; //TODO 变更玩家本身分数
                $urecode_rows = 1;
                $minsValue = abs($profitMoney) ; //实现扣分
                if (isset($profitMoney) && floatval($minsValue) != 0) { 
                    $user_rows = false;
                    $urecode_rows = $DBObj->insert($user_RECODE_Table, $userRecodes) ;          //添加资金记录；
                    if($profitMoney < 0){  
                        $user_rows = $DBObj ->where("uid",$yeUid)-> where("points",$minsValue,">=")-> update($user_Table, ["points" => $DBObj->dec($minsValue)]);     //玩家扣分
                    }else if($profitMoney > 0){ 
                        $user_rows = $DBObj ->where("uid",$yeUid)-> update($user_Table, ["points" => $DBObj->inc($profitMoney)]);                                     //玩家加分
                    }
                }
                //订单 [修改玩家订单状态]
                $order_rows = $DBObj -> where("userId",$yeUid)->where('gameNo',$room_No)->update($user_ORDER_Table, $orderFields);
                
                //TODO 统计表记录； 看有没有数据；
                $tongji_rows = ($isNation===3) ? 1 : 0;                           //TODO 统计表中是否有数据（机器人不写统计表）
                if($isNation !== 3){
                    
                    //TODO 统计表数据
                    $ifExist = $DBObj -> where('uid',$yeUid) -> where('createDate',$todayStr) -> getOne($user_TJ_Table,['uid']);
                    BaseEnum::Logger("统计表[UID={$yeUid}]  数据：({$todayStr})=>". json_encode($ifExist), 'user-login');
                    if(!empty($ifExist) && isset($ifExist['uid']) && intval($ifExist['uid']) == $yeUid) {
                        //TODO 统计表字段： allBetScore   总消费金额； userCellScore 有效下注  winScore(只算赢的)  revenueScore （抽水统计） profitScore   盈利金额 （纯盈利）  = 获得钱 - 有效下注；
                        $tongjdata = [
                            "allBetScore"      => $DBObj->inc($allBets) ,           //累加分
                            "gameBetNumber"     => $DBObj->inc(1)
                        ];
                        //没有变化就不操作字段
                        if (isset($cellScore) && floatval($cellScore) > 0) {
                            $tongjdata["userCellScore"] = $DBObj->inc($cellScore);  //累加分
                        }
                        if (isset($winMoney) && floatval($winMoney) > 0) {
                            $tongjdata["winScore"] = $DBObj->inc($winMoney);        //累加分
                        }
                        if (isset($minsValue) && floatval($minsValue) > 0) {
                            $tongjdata["profitScore"] = $DBObj->inc($profitMoney);  //累加分
                        }
                        if (isset($profitMoney) && $profitMoney > 0) {
                            $tongjdata["profitNumber"] = $DBObj->inc(1);            //累加次数
                        }
                        if (isset($watermoney) && $watermoney > 0) {
                            $tongjdata["revenueScore"] = $DBObj->inc($watermoney);            //累加分
                        }
                        $tongji_rows = $DBObj -> where("uid",$yeUid) ->where('createDate',$todayStr)  -> update($user_TJ_Table,$tongjdata,null);    //TODO 修改统计数据
                    }else{
                        $tongjdata = [
                            'uid'=>$yeUid,
                            'group_id'=> 1,
                            "recUid"=>$recUid,
                            'userName' => $userName,
                            "user_ident"=> 1,
                            "createDate"=> $todayStr,
                            "allBetScore"=> $allBets,
                            "userCellScore"=>$cellScore,
                            "profitScore"=>$profitMoney, 
                            "revenueScore"=>$watermoney, 
                            "winScore"=>$winMoney,
                            "gameBetNumber"=> 1,
                            "profitNumber"=> ($profitMoney>0) ? 1 : 0,
                            "created" =>  BaseEnum::getNowTime(false)
                        ];                        
                        $tongji_rows = $DBObj -> insert($user_TJ_Table,$tongjdata);     //TODO 新加统计数据；
                    }
                }
                //==========================================操作DB=====END===================================//
                //var_dump("u:{$user_rows} , order:{$order_rows} , recodes:{$urecode_rows},tongji:{$tongji_rows}"); exit ;
                BaseEnum::Logger("事务DB操作：[玩家-$yeUid ,isNation={$isNation}]]===>uRows:{$user_rows} , ordRows:{$order_rows} , recodRows:{$urecode_rows},tongjiRows:{$tongji_rows}", 'user-login');
                if($user_rows && $order_rows && $urecode_rows && $tongji_rows){     //完成一个玩家信息操作；只要有一个玩家的信息出错就全都回滚
                    $succOne = ["agentId"=> $recUid,"userName"=>$userName,"isNation" => $isNation,"points"=> $last_money,"changeMoney" => $profitMoney, "water" => $watermoney];
                    $lastUserBlance[$yeUid] = $succOne;     //完成一个订单操作；
                    $totalUpdate ++ ;
                }else{
                    throw new \Exception("结算订单异常[Uid={$yeUid}],gameNo:{$room_No},回滚事务撤消所有结算",-1);
                }
            } ##END FOREACH 
            
            BaseEnum::Logger("事务DB操作：(startTransaction)===>更新 Update:{$totalUpdate} , 总人数totalUNum:{$totalUNum} ====>   等待提交事务 ", 'user-login');
            if ($totalUpdate > 0 && $totalUNum > 0 && $totalUpdate === $totalUNum) { 
                $DBObj ->commit() ;         //确认提交事务；
                BaseEnum::Logger("事务DB操作：(startTransaction)===>提交Commit !!! ", 'user-login');
                //TODO 更新成功  最后返回数据处理：（包含信息：用户余额变化，变动资金多少，牌点数； 牌型倍数）
                TaskManager::getInstance()->async(function() use($room_No,$agentLists) {
                    
                    BaseEnum::Logger('结算完操作：异步执行统计===gameNO:' .$room_No . '  agentIds: '. json_encode($agentLists) , 'user-login');
                    SumComm::sumALLAgentLineData($agentLists);
                },function() use($room_No){
                    BaseEnum::Logger('异步执行统计结果===FINISH ！' .$room_No , 'user-login');
                });
                return $lastUserBlance;
            }
        } catch (\Exception $e) {
            $DBObj -> rollback();
            BaseEnum::Logger('结算失败：(dealUserOrder)===Exception-->code:' .$e->getCode() . '  msg: '. $e->getMessage(), 'user-login');
        }finally {
            DBTools::destPoolDB($DBObj);    //释放Pool
        }
        return FALSE;
    }
    
    /**
     * TODO 获取游戏记录数据
     * @param int $userid 玩家ID
     * @param boolean $gameRId 参数ID
     * @param number $pageNum 记录数据
     */
    public static function getUserPlayOrder($userid=FALSE,$gameId = FALSE ,$pageNum = 10, & $totalNum = 0 ,$fields = '*') {
        $gameModel = new GameOrder() ;
        $query = $gameModel -> dbConnector();
        if (isset($userid) && intval($userid) > 0) {
            $query = $query->where("userId",$userid);
        }
        if (isset($gameId) && intval($gameId) > 0) {
            $query = $query->where("gameId",$gameId);
        }
        $query = $query->where("dealing",1);
        $query3 = clone $query;             //复制一个版本出来；
        $fields = ['id','userId','gameId','roomId','account','gameNo','chairId','userCount','cellScore','profit','revenue','gameStartTime','gameEndTime','dealing'];
        $lists = $query -> orderBy("gameStartTime","DESC") -> get($gameModel->tableName,$pageNum,$fields);
        
        //统计总数；
        $totalNum = $query3 -> getValue($gameModel->tableName ,"COUNT(*)");
        $gameModel->clearPool();
        if ($totalNum>0 && !empty($lists)) {
            return $lists;
        }
        return FALSE;
    }
    
    
    
    /**
     * TODO 获取筹码列表
     * @param int $gameRId
     * @param number $smallEvery
     */
    public static function getRoomsChips($gameRId,& $chipMaxArray = []) {
        $lastChouMa = [];
        //TODO 从DB库中获取最新配置值；
        $roomInfo = BaseComm::GameRoomDetail($gameRId);
        $roomSetted = $roomInfo["roomSeting"] ?? FALSE; //所有配置值
        if (isset($roomSetted['bets']) && !empty($roomSetted['bets']) && sizeof($roomSetted['bets']) > 2) {
            foreach ($roomSetted['bets'] as &$ovl){ $ovl = intval($ovl) ;}
            $lastChouMa = $roomSetted['bets'];
        }
        
        //TODO 原始筹码值
        if(!$lastChouMa){
            switch ($gameRId){
                case 1050:$lastChouMa = [2,5,10,20,50];break;
                case 1051:$lastChouMa = [10,20,50,100,200];break;
                case 1052:$lastChouMa = [50,100,200,500,1000];break;
                case 1053:$lastChouMa = [200,500,1000,2000,5000];break;
                case 1110:$lastChouMa = [2,3,4,5,6,7];break;//抵住1,顶注14
                case 1111:$lastChouMa = [10,13,16,19,22,25];break;//底注5,顶注50
                case 1112:$lastChouMa = [40,50,60,70,80,100];break;
                case 1113:$lastChouMa = [100,130,160,190,220,250];break;
            }
        }
        
        //机器人筹码算法，最大的概率 1/2 ;  第二 1/5, 其它几个都是 1/10
        if (!empty($lastChouMa) && sizeof($lastChouMa) >= 5) {
            @sort($lastChouMa);
            $chipMaxArray = [];
            $maxVal = max($lastChouMa);
            foreach ($lastChouMa as $vin => $mlx){
                $pnum = 2;
                if ($vin==0) {
                    $pnum = 20; //最小占比： 1/2
                }else if($vin == 1){
                    $pnum = 4;   //最小占比： 1/5
                }else if($mlx == $maxVal){
                    $pnum = 1;
                }
                $chipMaxArray[$mlx] = $pnum; //设置筹码的概率值
                //for ($i = 0; $i < $pnum; $i++){ $chipMaxArray[] = $mlx; }
            }
        }
        return $lastChouMa;
    }
    
    
    /**
     * TODO 获取筹码列表
     * @param int $gameRId
     * @param number $smallEvery
     */
    public static function getRoomsLimitBets($gameRId,$smallEvery = 0) {
        $mut = 1;
        $default = ['min'=>5,"max"=>100];
        $limitRes = [];
        
        //TODO 从DB库中获取最新配置值；
        $roomInfo = BaseComm::GameRoomDetail($gameRId);
        $roomSetted = $roomInfo["roomSeting"] ?? FALSE; //所有配置值
        if (isset($roomSetted['limits']) && !empty($roomSetted['limits']) && sizeof($roomSetted['limits']) == 2) {
            $default = ['min'=> intval(min($roomSetted['limits'])),"max"=>intval(max($roomSetted['limits']))];
            $limitRes = ['seat1'=> $default, 'seat2'=> $default, 'seat3'=>$default,'seat4'=> $default];
            return $limitRes;
        }
        
        //TODO 原来的计算数据
        switch ($gameRId){
            case 1050:
            case 1051:
            case 1052:
            case 1053:
                if ($gameRId > 1050) {
                    $mut = ($gameRId-1050) * 2;
                    $default = ['min'=>$mut * 5,"max"=>$mut * 100];
                }
                $limitRes = ['seat1'=> $default, 'seat2'=> $default, 'seat3'=>$default,'seat4'=> $default];
                break;
            default: 
                break;
        }
        return $limitRes;
    }

    /** 查找游戏座位号的前一个或者后一个位置
     * @param $key
     * @param $arr
     * @param string $prevNext
     * @return
     */
    public static function getSeat($key, $arr, $prevNext='next')
    {
        if(empty($arr) || !isset($arr[$key])) return false;
        //查找键为$key元素的位置
        $offset = 0;
        foreach ($arr as $k => $v) {
            if($k == $key) break;
            ++$offset;
        }
        //查找哪个元素，前一个或后一个？
        if ('prev' == $prevNext) {
            $arr = array_reverse($arr);
        } else if ('next' == $prevNext) {
        } else {
            return false;
        }
        $iterator = new \ArrayIterator($arr);//实例化一个Iterator
        $iterator->seek($offset);//偏移量
        $iterator->next();//指向下一个元素
        if ($iterator->valid()){
            $item = $iterator->current();//下一个元素值
        }else{
            $item = $prevNext=='next' ? current($arr) : end($arr);
        }
        //返回数组当前指针指向元素的值
        return $item;
    }

    /**
     * @param $proArr 枚举概率数组 例如:[2=>40,1=>30,4=>20],下标是座位号
     * @return int|string 座位号
     */
    public static function getPropSeat($proArr=[]){
        $result = 0;
        if (empty($proArr)){
            return $result;
        }
        //概率数组的总概率精度
        $proSum = array_sum($proArr);
        //概率数组循环
        if ($proSum>1){
            foreach ($proArr as $key => $proCur) {
                $randNum = mt_rand(1,$proSum);
                if($randNum <= $proCur){
                    $result = $key;
                    break;
                }else{
                    $proSum -= $proCur;
                }
            }
        }else{
            $result = key($proArr);//取第一个下标即最大points值的座位号
        }
        unset($proArr);
        return $result;
    }

    //TODO 游戏记录
    public static function getGameRules($gameId){
        return [
            "gameId"   => $gameId,
            "gameName" => "抢庄牛牛",
            "gameDesc" => '玩法介绍：
定庄：
开局玩家开始抢庄家。抢庄倍数最高的玩家为本局庄家，抢庄倍数相同时携带游戏币越多坐庄概率越大。超时不抢庄，系统自动执行不抢操作。所有人都不抢庄，随机一名玩家坐庄。
闲家：
定庄后，所有闲家开始选择下注倍数。携带游戏币越多，最大可下注倍数越大，超时不下注，系统自动下最小倍数。
开始发牌：系统向每名玩家发五张手牌。
拼牌：玩家拿到五张手牌后即开始拼牌。
比牌：拼牌完成后，所有玩家亮出手牌。闲家只与庄家互比，闲家之间不互比。
结算公式：
A：房间底注
M：庄家牌型对应的倍数
N：闲家牌型对应的倍数
X：庄家抢庄倍数
Y：闲家下注倍数
庄家胜利所赢游戏币=A*M*X*Y-抽水
庄家失败所输游戏币=A*N*X*Y
闲家胜利时所赢游戏币=A*N*X*Y-抽水
闲家失败所输游戏币=A*M*X*Y
防以小博大机制：
本局带入多少游戏币，本局最多输赢多少游戏币。
比如您本局带入80，计算时你理论应得200.但假如您本局输了最多只输80，为了公平起见，您本局最多也只能赢80，而不是200.
抽水：赢家所赢金额的5%
特殊情况：
如果庄家不够输的情况，按照各闲家理论盈利比例结算输赢。比如庄家有100，一次性输完，A理论应该赢200，B理论应该赢200，C理论赢400，由于庄家钱不够输。按照ABC理论盈利占比，最终得出：A实际盈利=200/（200+200+400）*100=25，B实际盈利=200/（200+200+400）*100=25，C实际盈利=400/（200+200+400）*100=50
',
            'gameBetter'=> '牌面：10、J、Q、K的点数都算作10，其他牌按牌面数字计算。
没牛：5张牌中任意3张牌的点数之和都不为10的整数倍。
有牛：5张牌中有3张牌点数之和为10的整数倍，另外2张牌的牌面之和不为10的整数倍，则这2张点数之和的个位数即牛几。
牛牛：5张牌中有3张牌的点数之和为10的整数倍，且另外2张牌的点数相加也为10的整数倍。
四花牛：5张牌中有4张牌为花牌（J、Q、K）中的任意牌。
四炸：5张牌中有四张牌相同，第五张随意。
五小牛：五张牌的点数都小于五，且点数之和小于等于10。',
            'gameMutle'=> '牌型倍数：
五小牛：四倍
四炸：四倍
五花牛：四倍
四花牛：四倍
牛牛：三倍
牛七--牛九：两倍
牛一--牛六：一倍'
        ];
    }
}